#include "net.h"
#include "ip.h"
#include "ethernet.h"
#include "arp.h"
#include "icmp.h"

/**
 * @brief 处理一个收到的数据包
 * 
 * @param buf 要处理的数据包
 * @param src_mac 源mac地址
 */
void ip_in(buf_t *buf, uint8_t *src_mac)
{
    // Step1: 检查数据报长度
    uint8_t ip_hdr_len = sizeof(ip_hdr_t);
    if(buf->len < ip_hdr_len){
        printf("\nip_in: len(buf) < size(ip_hdr)\n");
        return;
    }
    // Step2: 报头检测
    ip_hdr_t *ipHdr = (ip_hdr_t *)(buf->data);
    if(ipHdr->version != IP_VERSION_4 ||
        swap16(ipHdr->total_len16) > buf->len ||
        ipHdr->ttl == 0){
        printf("\nip_in: check ip header error\n");
        return;
    }
    // Step3: 检查校验和
    uint16_t hdr_checksum16_temp = ipHdr->hdr_checksum16;
    ipHdr->hdr_checksum16 = 0;
    uint16_t temp = checksum16((uint16_t *)ipHdr, sizeof(ip_hdr_t));
    if(temp != hdr_checksum16_temp){
        printf("\nip_in: checksum16 error\n");
        return;
    }
    ipHdr->hdr_checksum16 = hdr_checksum16_temp;

    // Step4: 对比目的IP地址是否为本机IP地址
    if(memcmp(ipHdr->dst_ip, net_if_ip, NET_IP_LEN) != 0){
        printf("\nip_in: target_ip isn't my ip\n");
        return;
    }    
    
    // Step6: 检查填充字段
    if(buf->len > swap16(ipHdr->total_len16)){
        printf("\nip_in: has padding\n");
        buf_remove_padding(buf, buf->len - swap16(ipHdr->total_len16));
    }

    // Step5: 检查协议是否可达
    uint8_t hdr_protocol = ipHdr->protocol;
    uint8_t hdr_ip[NET_IP_LEN];
    memcpy(hdr_ip, ipHdr->src_ip, NET_IP_LEN); 
    if(net_protocol_exist(hdr_protocol)){
        printf("\nip_in: protocol unrecognized\n");
        icmp_unreachable(buf, hdr_ip, ICMP_CODE_PROTOCOL_UNREACH);
        return;
    }

    
    // Step6: 去掉IP头,去掉前保存有用信息
    buf_remove_header(buf, sizeof(ip_hdr_t));

    // Step7: 向上层传递数据报
    net_in(buf, hdr_protocol, hdr_ip);
}

/**
 * @brief 处理一个要发送的ip分片
 * 
 * @param buf 要发送的分片
 * @param ip 目标ip地址
 * @param protocol 上层协议
 * @param id 数据包id
 * @param offset 分片offset，必须被8整除
 * @param mf 分片mf标志，是否有下一个分片
 */
void ip_fragment_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol, int id, uint16_t offset, int mf)
{
    // Step1: 增加IP数据报头部
    buf_add_header(buf, sizeof(ip_hdr_t));

    // Step2: 填写IP数据报头部
    ip_hdr_t *hdr = (ip_hdr_t *)(buf->data);
    hdr->hdr_len = sizeof(ip_hdr_t) / IP_HDR_LEN_PER_BYTE;
    hdr->version = IP_VERSION_4;
    hdr->tos = 0;
    hdr->total_len16 = swap16(buf->len);
    hdr->id16 =  swap16(id);
    hdr->flags_fragment16 = swap16(mf ? (offset | IP_MORE_FRAGMENT): offset);
    hdr->ttl = IP_DEFALUT_TTL;
    hdr->protocol = protocol;
    memcpy(hdr->src_ip, net_if_ip, NET_IP_LEN);
    memcpy(hdr->dst_ip, ip, NET_IP_LEN);

    // Step3: 检验和
    hdr->hdr_checksum16 = 0;
    hdr->hdr_checksum16 = checksum16((uint16_t *)hdr, sizeof(ip_hdr_t));

    // Step4: arp_out()
    arp_out(buf, ip);
}

/**
 * @brief 处理一个要发送的ip数据包
 * 
 * @param buf 要处理的包
 * @param ip 目标ip地址
 * @param protocol 上层协议
 */
void ip_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol)
{
    // Step1: 若数据报长 小于等于 IP协议最大负载包长
    uint8_t ip_hdr_len = sizeof(ip_hdr_t);
    if(buf->len <= 1500 - ip_hdr_len){
        printf("\nip_out: len(buf) <= 1500 - size(ip_hdr)\n");
        uint16_t id = getIpIdentity();
        ip_fragment_out(buf, ip, protocol, id, 0, 0);
        return;
    }
    // Step2: 若数据报长 大于 IP协议最大负载包长
    uint16_t id = getIpIdentity(); // 这里和ip_out同时使用,原则上不应该同时使用
    int buf_len_use = buf->len;
    buf_t ip_buf;
    size_t bufOffset = 0;
    uint16_t MAX_BUF_LEN = 1500 - ip_hdr_len;
    while(buf_len_use > MAX_BUF_LEN){ // 要不要检查DF位
        buf_init(&ip_buf, MAX_BUF_LEN);
        memcpy(ip_buf.data, (buf->data + bufOffset * 8), MAX_BUF_LEN);
        ip_fragment_out(&ip_buf, ip, protocol, id, bufOffset, 1);
        bufOffset += MAX_BUF_LEN / IP_HDR_OFFSET_PER_BYTE;
        buf_len_use -= MAX_BUF_LEN;
    }
    /** 最后一个分组 */
    buf_init(&ip_buf, buf_len_use);
    memcpy(ip_buf.data, (buf->data + bufOffset * 8), buf_len_use);
    ip_fragment_out(&ip_buf, ip, protocol, id, bufOffset, 0);
}

uint16_t ip_id = 0;
uint16_t getIpIdentity(void){
    uint16_t res = ip_id;
    ip_id = (ip_id == 0xffff) ? 0: ip_id+1;
    return res;
}


/**
 * @brief 初始化ip协议
 * 
 */
void ip_init()
{
    net_add_protocol(NET_PROTOCOL_IP, ip_in);
}